/*
 * Powered By XY
 * Since 2020
 */

package com.xy.biz.mes.manager.impl;

import com.xy.biz.base.client.EquipmentEventBusClient;
import com.xy.biz.mes.dao.*;
import com.xy.biz.mes.domain.bo.*;
import com.xy.biz.mes.domain.converter.*;
import com.xy.biz.mes.domain.enums.ManufactureGoodsTypeEnum;
import com.xy.biz.mes.domain.enums.ManufactureOrderStatusEnum;
import com.xy.biz.mes.domain.enums.ManufactureOrderTypeEnum;
import com.xy.biz.mes.domain.enums.ManufactureTaskStatusEnum;
import com.xy.biz.mes.domain.po.*;
import com.xy.biz.mes.domain.query.*;
import com.xy.biz.mes.manager.ManufactureOrderManager;
import com.xy.biz.mes.manager.ManufactureTaskManager;
import com.xy.biz.rs.client.RecordTemplateClient;
import com.xy.biz.rs.domain.req.GetRecordTemplateRequest;
import com.xy.biz.rs.domain.req.GroupRecordTemplateRequest;
import com.xy.biz.rs.domain.resp.GetRecordTemplateGroupResponse;
import com.xy.lang.domain.Result;
import com.xy.lang.domain.tuples.Pair;
import com.xy.lang.enums.OccupyStatusEnum;
import com.xy.lang.util.ResultUtils;
import com.xy.tool.util.BeanUtilsEx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 *
 *
 * @author zsp
 * @date 2022-1-6
 */
@Service
public class ManufactureTaskManagerImpl implements ManufactureTaskManager {

    @Autowired
    private RecordTemplateClient recordTemplateClient;
    @Autowired
    private EquipmentEventBusClient equipmentEventBusClient;
    @Autowired
    private ManufactureOrderManager manufactureOrderManager;
    @Autowired
    private ManufactureTaskDao manufactureTaskDao;
    @Autowired
    private ManufactureTaskEquipmentDao manufactureTaskEquipmentDao;
    @Autowired
    private ManufactureTaskGoodsDao manufactureTaskGoodsDao;
    @Autowired
    private ManufactureTaskRecordDao manufactureTaskRecordDao;

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public int saveAllManufactureTask(Collection<ManufactureTaskBO> collection) {
        int affectedRows = manufactureTaskDao.saveAllManufactureTask(ManufactureTaskConverter.toPO(collection));
        if (affectedRows > 0) {
            List<ManufactureTaskGoodsBO> manufactureTaskGoodsBOList = collection.stream().flatMap(e -> e.getGoodsList().stream()).collect(Collectors.toList());
            if (!CollectionUtils.isEmpty(manufactureTaskGoodsBOList)) {
                affectedRows += manufactureTaskGoodsDao.saveAllManufactureTaskGoods(
                        ManufactureTaskGoodsConverter.toPO(manufactureTaskGoodsBOList));
            }
            List<ManufactureTaskEquipmentBO> manufactureTaskEquipmentBOList = collection.stream().flatMap(e -> e.getEquipmentList().stream()).collect(Collectors.toList());
            if (!CollectionUtils.isEmpty(manufactureTaskEquipmentBOList)) {
                affectedRows += manufactureTaskEquipmentDao.saveAllManufactureTaskEquipment(
                        ManufactureTaskEquipmentConverter.toPO(manufactureTaskEquipmentBOList));
            }
            Set<ManufactureOrderBO> manufactureOrderBOSet = collection.stream().map(ManufactureTaskBO::getManufactureOrder).collect(Collectors.toSet());
            if (!CollectionUtils.isEmpty(manufactureOrderBOSet)) {
                affectedRows += manufactureOrderManager.updateAllManufactureOrder(manufactureOrderBOSet);
            }
        }
        return affectedRows;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int updateManufactureTask(ManufactureTaskBO manufactureTaskBO) {
        int row = manufactureTaskDao.updateManufactureTask(ManufactureTaskConverter.toPO(manufactureTaskBO));
        if (row > 0) {
            if (!CollectionUtils.isEmpty(manufactureTaskBO.getGoodsList())) {
                manufactureTaskGoodsDao.updateAllManufactureTaskGoods(ManufactureTaskGoodsConverter.toPO(manufactureTaskBO.getGoodsList()));
            }
            if (!CollectionUtils.isEmpty(manufactureTaskBO.getRecordList())) {
                manufactureTaskRecordDao.updateAllManufactureTaskRecord(ManufactureTaskRecordConverter.toPO(manufactureTaskBO.getRecordList()));
            }
            if (!CollectionUtils.isEmpty(manufactureTaskBO.getEquipmentList())) {
                manufactureTaskEquipmentDao.updateAllManufactureTaskEquipment(ManufactureTaskEquipmentConverter.toPO(manufactureTaskBO.getEquipmentList()));
            }
            if (Objects.equals(manufactureTaskBO.getStatus(), ManufactureTaskStatusEnum.RECORDED.getCode())) {
                build(true, false, false, Collections.singletonList(manufactureTaskBO));
                Map<Long, ManufactureTaskGoodsBO> goodsInfoMap =
                        manufactureTaskBO.getGoodsList().stream()
                                .collect(Collectors.toMap(ManufactureTaskGoodsBO::getManufactureOrderGoodsId, e -> e, (e1, e2) -> e2));
                if (!CollectionUtils.isEmpty(goodsInfoMap)) {
                    List<ManufactureOrderGoodsBO> manufactureOrderGoodsBOList = manufactureOrderManager.listManufactureOrderGoodsById(goodsInfoMap.keySet());
                    manufactureOrderGoodsBOList.forEach(e -> {
                        if (goodsInfoMap.containsKey(e.getManufactureOrderGoodsId())) {
                            ManufactureTaskGoodsBO manufactureTaskGoodsBO = goodsInfoMap.get(e.getManufactureOrderGoodsId());
                            e.clearExpectId(e);
                            e.setFactCount(manufactureTaskGoodsBO.getFactCount());
                            e.setFactQuantity(manufactureTaskGoodsBO.getFactQuantity());
                        }
                    });
                    if (!CollectionUtils.isEmpty(manufactureOrderGoodsBOList)) {
                        manufactureOrderManager.updateAllManufactureOrderGoods(manufactureOrderGoodsBOList);
                    }
                }
            }
            check(manufactureTaskBO.getManufactureOrderId());
        }
        return row;
    }

    @Override
    public int updateAllManufactureTask(Collection<ManufactureTaskBO> collection) {
        return manufactureTaskDao.updateAllManufactureTask(ManufactureTaskConverter.toPO(collection));
    }

    private void check(long manufacturerOrderId) {
        List<ManufactureTaskPO> manufactureTaskPOList = manufactureTaskDao.listManufactureTask(ManufactureTaskQuery.max()
                .manufactureOrderId(manufacturerOrderId)
                .build());
        if (CollectionUtils.isEmpty(manufactureTaskPOList)) {
            return;
        }
        long finishedTaskCount = manufactureTaskPOList.stream().filter(e -> e.getStatus().equals(ManufactureTaskStatusEnum.REVIEWED.getCode())).count();
        ManufactureOrderBO manufactureOrder = manufactureOrderManager.getManufactureOrder(manufacturerOrderId);
        if (Objects.nonNull(manufactureOrder)) {
            if (manufactureTaskPOList.size() != finishedTaskCount
                    && !manufactureOrder.getStatus().equals(ManufactureOrderStatusEnum.MANUFACTURING.getCode())) {
                manufactureOrderManager.updateManufactureOrder(ManufactureOrderBO.builder()
                        .manufactureOrderId(manufactureOrder.getManufactureOrderId())
                        .status(ManufactureOrderStatusEnum.MANUFACTURING.getCode())
                        .build());
            } else if (manufactureTaskPOList.size() == finishedTaskCount
                    && !manufactureOrder.getStatus().equals(ManufactureOrderStatusEnum.FINISHED.getCode())) {
                manufactureOrderManager.updateManufactureOrder(ManufactureOrderBO.builder()
                        .manufactureOrderId(manufactureOrder.getManufactureOrderId())
                        .status(ManufactureOrderStatusEnum.FINISHED.getCode())
                        .build());
            }
        }
    }

    @Override
    public int deleteManufactureTask(Long manufactureTaskId) {
        return manufactureTaskDao.deleteManufactureTask(manufactureTaskId);
    }

    @Override
    public int deleteManufactureTaskRecord(Collection<Long> collection) {
        return manufactureTaskRecordDao.deleteManufactureTaskRecord(collection);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deleteManufactureTaskGoods(Collection<Long> collection) {
        List<ManufactureTaskGoodsPO> manufactureTaskGoodsPOList =
                manufactureTaskGoodsDao.listManufactureTaskGoodsById(collection);
        if (CollectionUtils.isEmpty(manufactureTaskGoodsPOList)) {
            return 0;
        }
        int row = manufactureTaskGoodsDao.deleteManufactureTaskGoods(collection);
        if (row > 0) {
            Set<Long> orderGoodsIdSet =
                    manufactureTaskGoodsPOList.stream().map(ManufactureTaskGoodsPO::getManufactureOrderGoodsId).collect(Collectors.toSet());
            row += manufactureOrderManager.deleteManufactureOrderGoods(orderGoodsIdSet);
        }
        return row;
    }

    @Override
    public ManufactureTaskBO getManufactureTask(ManufactureTaskByIdQuery query) {
        ManufactureTaskBO manufactureTaskBO = ManufactureTaskConverter.fromPO(manufactureTaskDao.getManufactureTask(query.getManufactureTaskId()));
        if (Objects.isNull(manufactureTaskBO)) {
            return null;
        }
        build(query.getWithGoodsFlag(), query.getWithRecordFlag(), query.getWithEquipmentFlag(), Collections.singletonList(manufactureTaskBO));
        return manufactureTaskBO;
    }

    @Override
    public List<ManufactureTaskBO> listManufactureTaskById(Collection<Long> collection) {
        return ManufactureTaskConverter.fromPO(manufactureTaskDao.listManufactureTaskById(collection));
    }

    @Override
    public List<ManufactureTaskBO> listManufactureTask(ManufactureTaskQuery query) {
        List<ManufactureTaskBO> manufactureTaskBOList =
                ManufactureTaskConverter.fromPO(manufactureTaskDao.listManufactureTask(query));
        build(query.getWithGoodsFlag(), query.getWithRecordFlag(), query.getWithEquipmentFlag(), manufactureTaskBOList);
        return manufactureTaskBOList;
    }

    private void build(boolean withGoodsFlag, boolean withRecordFlag, boolean withEquipmentFlag, List<ManufactureTaskBO> manufactureTaskBOList) {
        if (CollectionUtils.isEmpty(manufactureTaskBOList)) {
            return;
        }
        Set<Long> taskIdSet =
                manufactureTaskBOList.stream().map(ManufactureTaskBO::getManufactureTaskId).collect(Collectors.toSet());
        Map<Long, List<ManufactureTaskGoodsBO>> taskGoodsGroup = new HashMap<>();
        if (withGoodsFlag) {
            taskGoodsGroup.putAll(
                    ManufactureTaskGoodsConverter.fromPO(manufactureTaskGoodsDao.listManufactureTaskGoods(ManufactureTaskGoodsQuery.max()
                                    .manufactureTaskIdList(new ArrayList<>(taskIdSet)).build()))
                            .stream().collect(Collectors.groupingBy(ManufactureTaskGoodsBO::getManufactureTaskId)));
        }
        Map<Long, List<ManufactureTaskEquipmentBO>> taskEquipmentGroup = new HashMap<>();
        if (withEquipmentFlag) {
            taskEquipmentGroup.putAll(
                    ManufactureTaskEquipmentConverter.fromPO(manufactureTaskEquipmentDao.listManufactureTaskEquipment(ManufactureTaskEquipmentQuery.max()
                                    .manufactureTaskIdList(new ArrayList<>(taskIdSet)).build()))
                            .stream().collect(Collectors.groupingBy(ManufactureTaskEquipmentBO::getManufactureTaskId)));
        }
        Map<Long, List<ManufactureTaskRecordBO>> taskRecordGroup = new HashMap<>();
        Map<Long, List<GetRecordTemplateGroupResponse>> recordGroup = new HashMap<>();
        if (withRecordFlag) {
            taskRecordGroup.putAll(ManufactureTaskRecordConverter.fromPO(manufactureTaskRecordDao.listManufactureTaskRecord(ManufactureTaskRecordQuery.max()
                            .manufactureTaskIdList(new ArrayList<>(taskIdSet))
                            .build()))
                    .stream().collect(Collectors.groupingBy(ManufactureTaskRecordBO::getManufactureTaskId)));
            Set<Long> recordTemplateIdSet =
                    manufactureTaskBOList.stream().map(ManufactureTaskBO::getRecordTemplateId).collect(Collectors.toSet());
            if (!CollectionUtils.isEmpty(recordTemplateIdSet)) {
                Result<Map<Long, List<GetRecordTemplateGroupResponse>>> listResult = recordTemplateClient.groupRecordTemplateGroupByTemplateId(GroupRecordTemplateRequest.builder()
                        .recordTemplateIdList(new ArrayList<>(recordTemplateIdSet))
                        .withItem(true)
                        .build());
                recordGroup.putAll(ResultUtils.getOrException(listResult));
                recordTemplateClient.buildByRecordTemplate(manufactureTaskBOList,
                        recordTemplateIdSet,
                        ManufactureTaskBO::getRecordTemplateId,
                        (task, recordTemplate) -> task.setRecordTemplateName(recordTemplate.getRecordTemplateName()));
            }
        }
        if (!CollectionUtils.isEmpty(taskRecordGroup)
                || !CollectionUtils.isEmpty(taskEquipmentGroup)
                || !CollectionUtils.isEmpty(taskGoodsGroup)) {
            manufactureTaskBOList.forEach(e -> {
                Long manufactureTaskId = e.getManufactureTaskId();
                if (taskGoodsGroup.containsKey(manufactureTaskId)) {
                    e.setGoodsList(taskGoodsGroup.get(manufactureTaskId));
                }
                if (taskEquipmentGroup.containsKey(manufactureTaskId)) {
                    e.setEquipmentList(taskEquipmentGroup.get(manufactureTaskId));
                }
                if (taskRecordGroup.containsKey(manufactureTaskId)) {
                    e.setRecordList(taskRecordGroup.get(manufactureTaskId));
                }
                if (recordGroup.containsKey(e.getRecordTemplateId())) {
                    e.setRecordTemplateGroupList(recordGroup.get(e.getRecordTemplateId()));
                }
            });
        }
    }

    @Override
    public int countManufactureTask(ManufactureTaskQuery query) {
        return manufactureTaskDao.countManufactureTask(query);
    }

    @Override
    public int countManufactureTaskGoods(ManufactureTaskGoodsQuery query) {
        return manufactureTaskGoodsDao.countManufactureTaskGoods(query);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int saveAllManufactureTaskRecord(Collection<ManufactureTaskRecordBO> collection) {
        return manufactureTaskRecordDao.saveAllManufactureTaskRecord(ManufactureTaskRecordConverter.toPO(collection));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int saveAllManufactureTaskEquipment(Collection<ManufactureTaskEquipmentBO> collection) {
        int row = manufactureTaskEquipmentDao.saveAllManufactureTaskEquipment(ManufactureTaskEquipmentConverter.toPO(collection));
        if (row > 0) {
            Set<Long> occupyEquipmentIdSet = collection.stream().filter(e -> e.getUsage().equals(OccupyStatusEnum.USING.getCode()))
                    .map(ManufactureTaskEquipmentBO::getEquipmentId)
                    .collect(Collectors.toSet());
            if (!CollectionUtils.isEmpty(occupyEquipmentIdSet)) {
                equipmentEventBusClient.firerOccupyEquipmentHandler(new ArrayList<>(occupyEquipmentIdSet));
            }
        }
        return row;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int saveAllManufactureTaskGoods(Collection<ManufactureTaskGoodsBO> collection) {
        int row = manufactureTaskGoodsDao.saveAllManufactureTaskGoods(ManufactureTaskGoodsConverter.toPO(collection));
        if (row > 0) {
            List<ManufactureOrderGoodsBO> manufactureOrderGoodsBOList = BeanUtilsEx.copyPropertiesForNewList(collection, ManufactureOrderGoodsBO::new);
            row += manufactureOrderManager.saveAllManufactureOrderGoods(manufactureOrderGoodsBOList);
        }
        return row;
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public int checkManufactureTask(ManufactureTaskBO manufactureTaskBO) {
        int row = 0;
        if (Objects.equals(ManufactureTaskStatusEnum.REVIEWED.getCode(), manufactureTaskBO.getStatus())) {
            row = manufactureTaskDao.updateManufactureTask(ManufactureTaskPO.builder()
                    .status(ManufactureTaskStatusEnum.REVIEWED.getCode())
                    .manufactureTaskId(manufactureTaskBO.getManufactureTaskId())
                    .build());
            if (row > 0) {
                row += manufactureTaskGoodsDao.updateManufactureTaskGoodsByTaskId(ManufactureTaskGoodsPO.builder()
                        .checkFlag(true)
                        .updateTime(LocalDateTime.now())
                        .manufactureTaskId(manufactureTaskBO.getManufactureTaskId())
                        .build());
                row += manufactureTaskEquipmentDao.updateManufactureTaskEquipmentByTaskId(ManufactureTaskEquipmentPO.builder()
                        .checkFlag(true)
                        .manufactureTaskId(manufactureTaskBO.getManufactureTaskId())
                        .build());
                row += manufactureTaskRecordDao.updateManufactureTaskRecordByTaskId(ManufactureTaskRecordPO.builder()
                        .checkFlag(true)
                        .manufactureTaskId(manufactureTaskBO.getManufactureTaskId())
                        .build());
                check(manufactureTaskBO.getManufactureOrderId());
            }
        } else {
            if (!CollectionUtils.isEmpty(manufactureTaskBO.getEquipmentList())) {
                List<ManufactureTaskEquipmentBO> equipmentList = manufactureTaskBO.getEquipmentList();
                row += manufactureTaskEquipmentDao.updateAllManufactureTaskEquipment(ManufactureTaskEquipmentConverter.toPO(equipmentList));
            }
            if (!CollectionUtils.isEmpty(manufactureTaskBO.getGoodsList())) {
                List<ManufactureTaskGoodsBO> goodsList = manufactureTaskBO.getGoodsList();
                goodsList.forEach(e -> e.setUpdateTime(LocalDateTime.now()));
                row += manufactureTaskGoodsDao.updateAllManufactureTaskGoods(ManufactureTaskGoodsConverter.toPO(goodsList));
            }
            if (!CollectionUtils.isEmpty(manufactureTaskBO.getRecordList())) {
                List<ManufactureTaskRecordBO> recordList = manufactureTaskBO.getRecordList();
                row += manufactureTaskRecordDao.updateAllManufactureTaskRecord(ManufactureTaskRecordConverter.toPO(recordList));
            }
            build(true, true, true, Collections.singletonList(manufactureTaskBO));
            long unCheckCount = manufactureTaskBO.getGoodsList().stream().filter(e -> !e.getCheckFlag()).count();
            unCheckCount += manufactureTaskBO.getEquipmentList().stream().filter(e -> !e.getCheckFlag()).count();
            unCheckCount += manufactureTaskBO.getRecordList().stream().filter(e -> !e.getCheckFlag()).count();
            if (unCheckCount == 0) {
                int affectRow = manufactureTaskDao.updateManufactureTask(ManufactureTaskPO.builder()
                        .status(ManufactureTaskStatusEnum.REVIEWED.getCode())
                        .manufactureTaskId(manufactureTaskBO.getManufactureTaskId())
                        .build());
                if (affectRow > 0) {
                    check(manufactureTaskBO.getManufactureOrderId());
                }
            }
        }
        return row;
    }

    @Override
    public int updateAllManufactureTaskRecord(Collection<ManufactureTaskRecordBO> collection) {
        return manufactureTaskRecordDao.updateAllManufactureTaskRecord(ManufactureTaskRecordConverter.toPO(collection));
    }

    @Override
    public int updateAllManufactureTaskEquipment(Collection<ManufactureTaskEquipmentBO> collection) {
        int row = manufactureTaskEquipmentDao.updateAllManufactureTaskEquipment(ManufactureTaskEquipmentConverter.toPO(collection));
        if (row > 0) {
            Set<Long> freeEquipmentIdSet = new HashSet<>();
            Set<Long> occupyEquipmentIdSet = new HashSet<>();
            collection.forEach(e -> {
                if (e.getUsage().equals(OccupyStatusEnum.USING.getCode())) {
                    occupyEquipmentIdSet.add(e.getEquipmentId());
                }
                if (e.getUsage().equals(OccupyStatusEnum.FREE.getCode())) {
                    freeEquipmentIdSet.add(e.getEquipmentId());
                }
            });
            if (!CollectionUtils.isEmpty(freeEquipmentIdSet)) {
                equipmentEventBusClient.firerReleaseEquipmentHandler(new ArrayList<>(freeEquipmentIdSet));
            }
            if (!CollectionUtils.isEmpty(occupyEquipmentIdSet)) {
                equipmentEventBusClient.firerOccupyEquipmentHandler(new ArrayList<>(occupyEquipmentIdSet));
            }
        }
        return row;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int updateAllManufactureTaskGoods(Collection<ManufactureTaskGoodsBO> collection) {
        int row = manufactureTaskGoodsDao.updateAllManufactureTaskGoods(ManufactureTaskGoodsConverter.toPO(collection));
        if (row > 0) {
            List<ManufactureOrderGoodsBO> manufactureOrderGoodsBOList = BeanUtilsEx.copyPropertiesForNewList(collection, ManufactureOrderGoodsBO::new);
            if (!CollectionUtils.isEmpty(manufactureOrderGoodsBOList)) {
                row += manufactureOrderManager.updateAllManufactureOrderGoods(manufactureOrderGoodsBOList);
            }
        }
        return row;
    }

    @Override
    public int updateAllManufactureTaskGoodsResult(Collection<ManufactureTaskGoodsBO> collection) {
        return manufactureTaskGoodsDao.updateAllManufactureTaskGoodsResult(collection.stream().map(e ->
                        new Pair<>(ManufactureTaskGoodsConverter.toPO(e), e.getFromStatusSet()))
                .collect(Collectors.toList()));
    }

    @Override
    public List<ManufactureTaskRecordBO> listManufactureTaskRecord(ManufactureTaskRecordQuery query) {
        return ManufactureTaskRecordConverter.fromPO(manufactureTaskRecordDao.listManufactureTaskRecord(query));
    }

    @Override
    public List<ManufactureTaskEquipmentBO> listManufactureTaskEquipment(ManufactureTaskEquipmentQuery query) {
        return ManufactureTaskEquipmentConverter.fromPO(manufactureTaskEquipmentDao.listManufactureTaskEquipment(query));
    }

    @Override
    public List<ManufactureTaskGoodsBO> listManufactureTaskGoods(ManufactureTaskGoodsQuery query) {
        return ManufactureTaskGoodsConverter.fromPO(manufactureTaskGoodsDao.listManufactureTaskGoods(query));
    }

}
